home *** CD-ROM | disk | FTP | other *** search
/ GameStar 2004 April / Gamestar_61_2004-04_dvdb.iso / DVDStar / Editace / hltp.exe / {app} / Applications / QuArK / quarkpy / b2utils.py < prev    next >
Text File  |  2004-01-05  |  16KB  |  585 lines

  1. """   QuArK  -  Quake Army Knife
  2.  
  3. Various quadratic bezier utilities.
  4. """
  5.  
  6. #
  7. # by tiglari@hexenworld.com, May 2000
  8. #
  9. #THIS FILE IS PROTECTED BY THE GNU GENERAL PUBLIC LICENCE
  10. # FOUND IN FILE "COPYING.TXT"
  11. #
  12.  
  13. #$Header: /cvsroot/quark/runtime/quarkpy/b2utils.py,v 1.18 2002/12/29 04:18:33 tiglari Exp $
  14.  
  15.  
  16. import math
  17. import quarkx
  18. from maputils import *
  19.  
  20. #
  21. # Here should go things of general utility for managing
  22. #  quadratic bezier patches
  23.  
  24.  
  25.  
  26. within45 = math.cos(deg2rad*45)
  27.  
  28. def iseven(num):
  29.   return not divmod(num,2)[1]
  30.  
  31. def linearcomb(C, P):
  32.  "linear combination"
  33.  return reduce(lambda x,y:x+y, map(lambda p,c:c*p,C,P))
  34.  
  35. def extend_distance_by(v1, v2, ext):
  36.   "v1 plus distance from v1 to v2 times ext"
  37.   return v1 + ext*(v2-v1)
  38.  
  39.  
  40. #
  41. # Some useful things for Quadratic Beziers
  42. #
  43.  
  44. def rowofcp(cp, i):
  45.     return cp[i]
  46.     
  47. def colofcp(cp, j):
  48.     return map(lambda row,j=j:row[j], cp)
  49.     
  50. def lengthof(line, divs):
  51.     if divs<0:
  52.       return
  53.     sum=0
  54.     for i in range((len(line)-1)/2):
  55.       k = 2*i
  56.       sum=sum+lengthofseg(line[k], line[k+1], line[k+2], divs)
  57.     return sum
  58.     
  59. #
  60. # If this were going to get out into generally applying non-UI
  61. #   routines, it might be worth shifting it into delphi.
  62. #
  63. def lengthofseg(p0, p1, p2, divs):
  64.     "approximates length of b2 line segment, splitting into 2^divs segments"
  65.     if divs == 0:
  66.        return abs(p2-p0)
  67.     else:
  68.        m = b2midpoint(p0, p1, p2)
  69.        q1 = b2qtpoint(p0, p1, p2)
  70.        q3 = b2qt3point(p0, p2, p2)
  71.        m0 = b2midcp(p0, q1, m)
  72.        m1 = b2midcp(m, q3, p2)
  73.        return lengthofseg(p0, m0, m, divs-1)+lengthofseg(m, m1, p2, divs-1)
  74.        
  75. def b2midpoint(p0, p1, p2):
  76.   "midpoint of the b2 line for the three points"
  77.   return 0.25*p0 + 0.5*p1 + 0.25*p2
  78.   
  79. def b2qtpoint(p0, p1, p2):
  80.   "1 quarter point of the b2 line for the three points"
  81.   return (9/16.0)*p0+(3/8.0)*p1+(1/16.0)*p2
  82.  
  83. def b2qt3point(p0, p1, p2):
  84.   "3 quarter point of the b2 line for the three points"
  85.   return (1/16.0)*p0+(3/8.0)*p1+(9/16.0)*p2
  86.  
  87. def b2midcp(p0, m, p2):
  88.   "cp to get b2 line from p0 to p2 passing thru m"
  89.   return 2.0*m-0.5*(p0+p2)
  90.  
  91.  
  92.  
  93. def interpolateGrid(p0, p1, p2, p3, h=3, w=3):
  94.     "makes a bezier from the four points"
  95.     "p0, ..., p1 top row, p2,..., p3 bottom"
  96.     cp = []
  97.     h, w = float(h), float(w)
  98.     H, W = h-1, w-1
  99.     upfront, upback = (p2-p0)/(h-1), (p3-p1)/(h-1)
  100.     for i in range(h):
  101.         front, back = p0+i*upfront, p1+i*upback
  102.         across = (back-front)/(w-1)
  103.         row = []
  104.         for j in range(w):
  105.             #
  106.             # non 3x3 cp's need to be 5-space
  107.             #
  108.             row.append(quarkx.vect((front+j*across).tuple+(i/H,j/W)))
  109.         cp.append(row)
  110.     return cp
  111.  
  112.  
  113. def transposeCp(cp):
  114.     "returns cp transposed"
  115.     new = map(lambda x:[],range(len(cp[0])))
  116.     for j in range(len(new)):
  117.         for row in cp:
  118.             new[j].append(row[j])
  119.     return new
  120.  
  121.  
  122. #
  123. # This seems to be needed because copy.deepcopy() non sembra functionare
  124. #
  125. def copyCp(cp):
  126.     "returns a copy of the cp array"
  127.     return map(lambda row:map(lambda v:v, row), cp)
  128.  
  129. def mapCp(f, cp):
  130.     "returns a new cp array with f applied to each member of cp"
  131.     return map(lambda row,f=f:map(f, row), cp)
  132.  
  133. def texcpFromCp(cp, cp2):
  134.     "tex coords of 2nd cp net are transferred to first"
  135.     if len(cp)!=len(cp2) or len(cp[0])!=len(cp2[0]):
  136.       quarkx.msgbox("transferTexcp dimension mismatch",2,4)
  137.       return
  138.     def maprow(row, row2):
  139.         return map(lambda v, v2:quarkx.vect(v.xyz+v2.st), row, row2)
  140.     return map(maprow, cp, cp2)
  141.  
  142.     
  143. def listCp(cp):
  144.     cp2 = []
  145.     for row in cp:
  146.         cp2.append(list(row))
  147.     return cp2
  148.  
  149. #
  150. # The basic idea here is that if the patch is sitting right over
  151. #  the face, the three points p0, p1, p2 should get the patch .st
  152. #  coordinates (0,0), (1,0) and (0, 1) respectively.
  153. #
  154. def texcpFromFace(cp, face, editor):
  155.     "returns a copy of cp with the texture-scale of the face projected"
  156.     #    
  157.     # Note special code, which inhibits recentering threepoints
  158.     #
  159.     p0, p1, p2 = face.threepoints(6)
  160.     
  161.     def axis(p, p0=p0):
  162.         "turns a texp point into axis for computing b2 texcp's"
  163.         return (p-p0).normalized/abs(p-p0)
  164.  
  165.     def project(v, p0=p0, (s_axis, t_axis)=map(axis, (p1, p2))):
  166.         # note the wacko sign-flip for t
  167.         return quarkx.vect(v.xyz + ((v-p0)*s_axis, -(v-p0)*t_axis))
  168.  
  169.     return mapCp(project, cp)
  170.  
  171. def texFromFaceToB2(b2, face, editor):
  172.     "copies texture and scale from face to bezier"
  173.     b2["tex"] = face["tex"]
  174.     b2.cp = texcpFromFace(b2.cp, face, editor)
  175.  
  176. def b2FromCpFace(cp, name, face, editor):
  177.     b2 = quarkx.newobj(name+':b2')
  178.     b2.cp =  texcpFromFace(cp, face, editor)
  179.     b2["tex"]=face["tex"]
  180.     return b2
  181.  
  182. def cpFrom2Rows(row0, row2, bulge=None):
  183.     "makes cp from top & bottom rows & fills in middle"
  184.     if bulge is None:
  185.       bulge=(0.5, 1.0)
  186.     cp = [row0, None, row2]
  187.     cp[1] = map(lambda x, y, h=bulge[0]:h*x+(1-h)*y, cp[0], cp[2]) 
  188.     if bulge[1]!=1:
  189.         c=reduce(lambda x,y:x+y,cp[1])/float(len(cp[1]))
  190.         cp[1]=map(lambda v,c=c,b=bulge[1]:c+b*(v-c), cp[1])
  191.     return cp
  192.  
  193.  
  194. def b2From2Rows(row0, row2, texface, name, bulge=None, subdivide=1, subfunc=None):
  195.      cp = cpFrom2Rows(row0, row2, bulge)
  196.      if subdivide>1:
  197.          cp = subdivideRows(subdivide,cp, subfunc)
  198.      b2 = quarkx.newobj(name+":b2")
  199.      b2["tex"] = texface["tex"]
  200.      b2.cp = texcpFromFace(cp, texface, None)
  201.      return b2
  202.  
  203.  
  204. #
  205. # If u and v are images of the parameter axes, this matrix
  206. #  gives the associated linear mapping
  207. #
  208. def colmat_uv1(u,v):
  209.     "returns column matrix of u, v, and z axis vector tuples"
  210.     return quarkx.matrix((u[0], v[0], 0),
  211.                          (u[1], v[1], 0),
  212.                          (u[2], v[2], 1))
  213.  
  214. #
  215. # bcp is a flat array of control points, cp an array `rolled up'
  216. # along the columns.  The idea is to readjust the texture coordinates
  217. # of the bcp to compensate for distortion along the columns
  218. #
  219. def undistortColumns(cp):
  220.     ncp = copyCp(cp)   # this is what we return, after diddling it
  221.     h, w = len(cp), len(cp[0])
  222.     for j in range(w):  # for each column
  223. #        squawk(" col %d"%j)
  224.         #
  225.         # get info about distances between cp's
  226.         #
  227.         lengths = []
  228.         sum = 0
  229.         for i in range(1, h):
  230.             dist = abs(cp[i][j]-cp[i-1][j])
  231.             sum = sum + dist
  232.             lengths.append(sum)
  233.         if sum == 0:
  234.             sum = 1
  235.         #
  236.         # now rearrange texture cp's of bcp
  237.         #
  238.         start, end = cp[0][j], cp[h-1][j]
  239.         texstart, texend = map(lambda v:quarkx.vect(v.s, v.t, 0), (start,end))
  240.         texgap = texend-texstart
  241.         #
  242.         #  Now do it
  243.         #
  244. #        squawk('rockin')
  245.         for i in range(1,h-1):
  246.             s, t, x = (texstart + (lengths[i-1]/sum)*texgap).tuple
  247.             ncp[i][j]=quarkx.vect(cp[i][j].xyz+(s, t))
  248. #    squawk(`nbcp`)
  249.     return ncp      
  250.       
  251. def undistortRows(cp):
  252.     cp = transposeCp(cp)
  253.     cp = undistortColumns(cp)
  254.     return transposeCp(cp)
  255.  
  256. #
  257. # Getting approximate tangent planes at control points.
  258. #
  259.  
  260. #
  261. # The idea here is that at odd-numbered and quilt-end points, you
  262. #  take the actual derivatives, at intermedient end points you
  263. #  take the average of the derivative in and the derivative out.
  264. #  
  265. def dpdu(cp, i, j):
  266.   h = len(cp)
  267.   if i==0:
  268.     return 2*(cp[1][j]-cp[0][j])
  269.   elif i==h-1:
  270.     return 2*(cp[i][j]-cp[i-1][j])
  271.   else:
  272.     return (cp[i+1][j]-cp[i-1][j])
  273.     
  274. def dpdv(cp, i, j):
  275.   w = len(cp[0])
  276.   if j==0:
  277.     return 2*(cp[i][j+1]-cp[i][j])
  278.   elif j==w-1:
  279.     return 2*(cp[i][j]-cp[i][j-1])
  280.   else:
  281.     return cp[i][j+1]-cp[i][j-1]
  282.     
  283. def tanAxes(cp, i, j):
  284.   return dpdu(cp, i, j).normalized, dpdv(cp, i, j).normalized
  285.   
  286. #
  287. #  Derivative matrix for parameter->space mappings and
  288. #    parameter->plane mappings, at corners.
  289. #  Not defined at non-corners due to greater complexity and/or
  290. #    ill-definition (crinkles=no deriv at even-indexed cp's)
  291. #
  292. def d5(cp, (i, j)):
  293.     dSdu = dSdv = None
  294.     if i==0:
  295.         dSdu = cp[1][j]-cp[0][j]
  296.     elif i==len(cp)-1:
  297.         dSdu = cp[i][j]-cp[i-1][j]
  298.     if j==0:
  299.         dSdv = cp[i][1]-cp[i][0]
  300.     elif j==len(cp[0])-1:
  301.         dSdv = cp[i][j]-cp[i][j-1]
  302.     return dSdu, dSdv  
  303.     
  304.  
  305. #def faceTexFromCph(cph, face, editor):
  306. def texPlaneFromCph(cph, editor):
  307.     "projects texture-scale at cp handle to face, returning copy of face"
  308.     b2 = cph.b2
  309.     d5du, d5dv = d5(b2.cp, cph.ij)
  310.     if d5du is None or d5dv is None:
  311.         return None
  312.     #
  313.     # Derivatives of parameter->space and parameter->tex maps.
  314.     # S for space, T for texture (cap so diff from patch coords)
  315.     #
  316.     dSdp = colmat_uv1(d5du.xyz, d5dv.xyz)
  317.     dTdp = colmat_uv1((d5du.s, d5du.t, 1),
  318.                       (d5dv.s, d5dv.t, 1))
  319.     Mat = dSdp*~dTdp
  320.     #
  321.     # This mapping is the texture scale & offset (differential
  322.     #   of texture->space mapping)
  323.     #
  324.     def mapping(t3, offset=b2.cp[0][0], Mat=Mat):
  325.         texoffset = quarkx.vect(offset.s, offset.t, 0)
  326.         return Mat*(quarkx.vect(t3)-texoffset)+quarkx.vect(offset.xyz)
  327.     #
  328.     # Apply the texture differential to origin & two axes of texture
  329.     #   space.  Note wierdass sign-reversal (beaucoup de tah, Bill)
  330.     #
  331.     texp = map(mapping,((0,0,0),(1,0,0),(0,-1,0)))
  332.     #
  333.     # Now first project the texture onto a face tangent to the patch,
  334.     #   then project it onto the face we want.
  335.     #
  336.     new = quarkx.newobj("face:f")
  337. #    new = face.copy()
  338.     new.setthreepoints(texp,1)
  339.     new["tex"]=b2["tex"]
  340.     new.setthreepoints(texp,2,editor.TexSource)
  341.     return new
  342.  
  343. #
  344. #   The counterclockwise traversal of the edges
  345. #     supports using arithmetic to figure out how
  346. #     to `rotate' things for patch-merger & knitting
  347. #
  348. P_FRONT = 0   # first column of patch
  349. P_TOP = 1     # last row of patch 
  350. P_BACK = 2    # last column of patch
  351. P_BOTTOM = 3  # row 0 of patch
  352.  
  353.  
  354. def RotateCpCounter1(cp):
  355.     "returns a cp net where the old P_BACK is now P_TOP"
  356.     ncp = []
  357.     h = len(cp)
  358.     w = len(cp[0])
  359.     for j in range(w):
  360.         ncp.append(map(lambda i,cp=cp,k=w-j-1:cp[i][k],range(h)))
  361.     return ncp
  362.  
  363. def RotateCpCounter2(cp):
  364.     ncp = []
  365.     h = len(cp)
  366.     for i in range(h):
  367.       row = cp[h-i-1]
  368.       row = list(row)
  369.       row.reverse()
  370.       ncp.append(row)
  371.     return ncp
  372.  
  373. def RotateCpCounter(i, cp):
  374.     if i==0:
  375.         return copyCp(cp)
  376.     i=i%4
  377.     if i==0:
  378.         return copyCp(cp)
  379.     if i==1:
  380.         return RotateCpCounter1(cp)
  381.     if i==2:
  382.         return RotateCpCounter2(cp)
  383.     if i==3:
  384.         return RotateCpCounter1(RotateCpCounter2(cp))
  385.  
  386. def twistedRows(cp1, cp2):
  387.     lr, lc = len(cp1)-1, len(cp1[0])-1
  388.     e1, e2 = cp1[0][lc], cp1[lr][lc]
  389.     b1, b2 = cp2[0][0], cp2[lr][0]
  390.     if abs(e1-b1)>abs(e1-b2) and abs(e2-b2)>abs(e2-b1):
  391.        return 1
  392.     return 0
  393.       
  394.       
  395. def joinCp((tp1,X), cp1, (tp2,Y), cp2):
  396.     "returns cp1 extended to include cp2, assumes preconditions"
  397. #    squawk(`tp1-P_BACK`)
  398.     cp1 = RotateCpCounter(P_BACK-tp1, cp1)
  399.     cp2 = RotateCpCounter(P_FRONT-tp2, cp2)
  400. #    squawk(`cp1`)
  401. #    squawk(`cp2`)
  402.     twisted = twistedRows(cp1,cp2)
  403.     if twisted:
  404.        cp1.reverse()
  405.     ncp = map(lambda row1, row2:row1+row2[1:], cp1, cp2)
  406.     if twisted:
  407.        ncp.reverse()
  408.     return RotateCpCounter(tp1-P_BACK, ncp)
  409.  
  410. def knitCp((tp1,X), cp1, (tp2,Y), cp2):
  411.     "returns cp1 with edge knitted to cp2, assumes preconditions"
  412. #    squawk(`tp1-P_BACK`)
  413.     cp1 = RotateCpCounter(P_BACK-tp1, cp1)
  414.     cp2 = RotateCpCounter(P_FRONT-tp2, cp2)
  415. #    squawk(`cp1`)
  416. #    squawk(`cp2`)
  417.     last = len(cp1[0])-1
  418. #    ncp = copyCp(cp1)
  419.     twisted = twistedRows(cp1, cp2)
  420.     if twisted:
  421.       cp1.reverse()
  422.     for i in range(len(cp1)):
  423.         cp1[i][last]=cp2[i][0]
  424.     if twisted:
  425.       cp1.reverse()
  426.     squawk('done')
  427.     return RotateCpCounter(tp1-P_BACK, cp1)
  428.     
  429. def b2Point(u, p0, p1, p2):
  430.     return u*u*(p0-2*p1+p2)+u*(-2*p0+2*p1)+p0
  431. #    return (1-u)*(1-u)*p0 + 2*u*(1-u)*p1 + p2*u*u
  432.     
  433. #
  434. # This does 1 seg with 3 cp's, the crappy way.
  435. #
  436. def subdivideLine(n, p0, p1, p2):
  437.     "for n>=1, return 1+2n-tuple defining bezier quilt mesh line"
  438. #    squawk('subdiv '+`n`)
  439.     if n < 1: return
  440.     if n==1: return [p0, p1, p2]
  441.     retval = [p0]
  442.     last = p0
  443.     for i in range(n):
  444.         q = b2Point((i+.5)/n, p0, p1, p2)
  445.         p = b2Point((i+1.0)/n, p0, p1,p2)
  446.         m = b2midcp(last,q,p)
  447.         retval.append(m)
  448.         retval.append(p)
  449.         last = p
  450.     return retval
  451.  
  452. #
  453. # This is supposed to do a whole 1+2*n line
  454. #
  455. def subdivideRow(n, row, subfunc=None):
  456.     if subfunc==None:
  457.         subfunc=subdivideLine
  458.     length = len(row)
  459.     result = [row[0]]
  460.     for i in range(0,length-1,2):
  461. #       squawk(`i`)
  462. #       squawk(`result`)
  463.        line = subfunc(n, row[i], row[i+1], row[i+2])
  464. #       squawk(`line`)
  465.        result = result + subfunc(n, row[i], row[i+1], row[i+2])[1:]
  466. #    squawk(`result`)
  467.     return  result
  468.     
  469.  
  470. def subdivideRows(n, cp, func=None):
  471.     return map(lambda row,n=n,f=func:subdivideRow(n, row,f),cp)
  472.  
  473.  
  474. def subdivideColumns(n, cp, func=None):
  475.     return transposeCp(subdivideRows(n,transposeCp(cp),func))
  476.     
  477.  
  478. #
  479. # Attempt at a better circle approximation.
  480. #  the idea is to think of the b2 curve as an approximation
  481. #  to an image of a quarter-circle.
  482. # Derived from a suggestion by Alex Haarer.
  483. #
  484. def arcSubdivideLine(n, p0, p1, p2):
  485.     mat = matrix_u_v(p0-p1, p2-p1)
  486.     halfpi = math.pi/2.0
  487.     points = [quarkx.vect(1,0,0)]
  488.     last = points[0]
  489.     lastdir = quarkx.vect(-1,0,0)
  490.     for i in range(n):
  491.         a = halfpi*(i+1)/n
  492.         next = quarkx.vect(1.0-math.sin(a), 1.0-math.cos(a), 0)
  493.         nextdir = quarkx.vect(-math.cos(a), math.sin(a), 0)
  494.         mid = intersectionPoint2d(last,lastdir, next, nextdir)
  495.         points.append(mid)
  496.         points.append(next)
  497.         last = next
  498.         lastdir = nextdir
  499.     points = map (lambda v,mat=mat,d=p1:d+mat*v, points)
  500.     return points
  501.     
  502. #
  503. # get i, j from index position (row-after-row listing)
  504. #   think of a better name for this
  505. #
  506. def cpPos(p,b2):
  507.     i, j = divmod(p, b2.W)
  508.     return int(i), int(j)
  509.  
  510. def writecps(cp):
  511.     debug('cp: ')
  512.     for row in range(len(cp)):
  513.       for  col in range(len(cp[row])):
  514.         point = cp[row][col]
  515.         try:
  516. #          debug(" %d,%d: s: %6.2f t: %6.2f"%(row, col, cp[row][col][3], cp[row][col][4]))
  517.            debug(" %d,%d: s: %6.2f, t: %6.2f"%(row, col, point.s, point.t))
  518.         except:
  519.           debug(" notexcoords")
  520.  
  521.  
  522.  
  523. # ----------- REVISION HISTORY ------------
  524. #
  525. #
  526. #$Log: b2utils.py,v $
  527. #Revision 1.18  2002/12/29 04:18:33  tiglari
  528. #transfer fixes from 6.3
  529. #
  530. #Revision 1.17.10.1  2002/12/29 02:57:59  tiglari
  531. #texcpfromface now uses threepoints(6) to avoid recentering tex coords
  532. #
  533. #Revision 1.17  2001/03/20 11:01:51  tiglari
  534. #credit added
  535. #
  536. #Revision 1.16  2000/12/30 05:27:11  tiglari
  537. #cpPos function for mapping index position to i, j coordinates
  538. #
  539. #Revision 1.15  2000/09/04 21:24:23  tiglari
  540. #added procedures for better circular arc segments
  541. #
  542. #Revision 1.14  2000/09/02 11:22:35  tiglari
  543. #generalized subdivideRows/Columns to arbitrary quilts
  544. #
  545. #Revision 1.13  2000/08/23 12:12:34  tiglari
  546. #Added support for edge knitting; fixed join bug
  547. #
  548. #Revision 1.12  2000/07/24 12:47:40  tiglari
  549. #listCP function added
  550. #
  551. #Revision 1.11  2000/07/23 08:40:44  tiglari
  552. #faceTexFromCph removed; texPlaneFromCph added
  553. #
  554. #Revision 1.10  2000/06/26 22:51:55  tiglari
  555. #renaming: antidistort_rows/columns->undistortRows/Colunmns,
  556. #tanaxes->tanAxes, copy/map/transposecp->copy/map/transposeCP
  557. #
  558. #Revision 1.9  2000/06/25 23:48:01  tiglari
  559. #Function Renaming & Reorganization, hope no breakage
  560. #
  561. #Revision 1.8  2000/06/25 11:00:50  tiglari
  562. #fixed antidistortion crash when sum=0.  still wrong but doesn't crash
  563. #
  564. #Revision 1.7  2000/06/22 22:38:37  tiglari
  565. #added interpolateGrid (replacing an unused fn with a goofy name)
  566. #
  567. #Revision 1.6  2000/06/12 11:20:45  tiglari
  568. #Redid antidistort_columns, added antidistort_rows
  569. #
  570. #Revision 1.5  2000/06/04 03:21:25  tiglari
  571. #distortion reduction (elimination) for `rolled up' columns
  572. #
  573. #Revision 1.4  2000/06/03 18:01:28  alexander
  574. #added cvs header
  575. #
  576. #Revision 1.3  2000/06/03 12:59:33  tiglari
  577. #fixed arch duplicator maploading problem, hopefully
  578. #
  579. #Revision 1.2  2000/06/02 16:00:22  alexander
  580. #added cvs headers
  581. #
  582. #
  583. #